Flutter Android Hybrid composition 实现原理

Hybrid composition 是《Flutter 内嵌原生视图能力》在 Android 侧的第二代实现。在本文中,将介绍它的实现原理。

iOS Hybrid Composition

在 iOS 下使用 Hybrid Composition,采用另外一套写法,基于 Flutter 的 UiKitView,具体可参见 Hybrid Composition · flutter/flutter Wiki。本文介绍 Android 端实现原理。

Hybrid composition 的实现原理比 Flutter Android Virtual Display 实现原理 要复杂一些,原因是 Hybrid composition 模式大量耦合 Flutter Engine 渲染流水线,流水线的相当一部分都是在对 Hybrid composition PlatformView 做处理。因此,要捋清 Hybrid composition 的原理,就要捋清 Flutter 渲染流水线


在 Flutter 侧,开发者直接打交道的组件是 PlatformViewLink

class FooPlatformView extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return PlatformViewLink(
       viewType: 'webview',
       surfaceFactory: (BuildContext context, PlatformViewController controller) {
        return PlatformViewSurface(
            gestureRecognizers: gestureRecognizers,
            controller: controller,
            hitTestBehavior: PlatformViewHitTestBehavior.opaque,
        );
       },
       onCreatePlatformView: (PlatformViewCreationParams params) {
	      return PlatformViewsService.initSurfaceAndroidView(
	        id: params.id,
	        viewType: 'webview',
	        layoutDirection: TextDirection.ltr,
	        creationParams: params,
	        creationParamsCodec: StandardMessageCodec()
	      )..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
	       ..create();
       }
    );
   }
}

PlatformViewLink 组件的最底层 Render Object 是 PlatformViewRenderBox


Step2:渲染流水线 BeginFrame

下面进入 Flutter 渲染流水线 部分。Hybrid composition PlatformView 的绘制是从 BeginFrame 开始的,但这里的 BeginFrame,并不是 Flutter 渲染流水线 中《Step3:BeginFrame 开启新的一帧》中所说的 BeginFrame。

实际上,Hybrid composition PlatformView 的 BeginFrame 对应于Flutter 渲染流水线 中的《Step7-1 Surface 绘制》。这一步,已经是 Flutter UI 流水线渲染完成,将 Display List 传给 Rasterizer 进行光栅化绘制了。

在 DrawToSurface 这一步,准备往 Surface 上绘制时:

// Rasterizer::DrawToSurfaceUnsafe
RasterStatus Rasterizer::DrawToSurfaceUnsafe(
    FrameTimingsRecorder& frame_timings_recorder,
    flutter::LayerTree& layer_tree) {

  SkCanvas* embedder_root_canvas = nullptr;
  // Hybrid Composition 下,PlatformView 的帧绘制是从这里开始的
  if (external_view_embedder_) {
    external_view_embedder_->BeginFrame(
        layer_tree.frame_size(), surface_->GetContext(),
        layer_tree.device_pixel_ratio(), raster_thread_merger_);
    embedder_root_canvas = external_view_embedder_->GetRootCanvas();
  }

  // On Android, the external view embedder deletes surfaces in `BeginFrame`.
  //
  // Deleting a surface also clears the GL context. Therefore, acquire the
  // frame after calling `BeginFrame` as this operation resets the GL context.
  auto frame = surface_->AcquireFrame(layer_tree.frame_size());
  if (frame == nullptr) {
    return RasterStatus::kFailed;
  }

  // If the external view embedder has specified an optional root surface, the
  // root surface transformation is set by the embedder instead of
  // having to apply it here.
  SkMatrix root_surface_transformation =
      embedder_root_canvas ? SkMatrix{} : surface_->GetRootTransformation();

  auto root_surface_canvas =
      embedder_root_canvas ? embedder_root_canvas : frame->SkiaCanvas();

  // 帧合成器
  auto compositor_frame = compositor_context_->AcquireFrame(
      surface_->GetContext(),         // skia GrContext
      root_surface_canvas,            // root surface canvas
      external_view_embedder_.get(),  // external view embedder
      root_surface_transformation,    // root surface transformation
      true,                           // instrumentation enabled
      frame->framebuffer_info()
          .supports_readback,  // surface supports pixel reads
      raster_thread_merger_    // thread merger
  );
  if (compositor_frame) {
    compositor_context_->raster_cache().PrepareNewFrame();

	//...

    // 光栅化
    RasterStatus raster_status =
        compositor_frame->Raster(layer_tree, false, &damage);
    if (raster_status == RasterStatus::kFailed ||
        raster_status == RasterStatus::kSkipAndRetry) {
      return raster_status;
    }

    SurfaceFrame::SubmitInfo submit_info;
    submit_info.frame_damage = damage.GetFrameDamage();
    submit_info.buffer_damage = damage.GetBufferDamage();

    frame->set_submit_info(submit_info);

    // 上屏!
    if (external_view_embedder_ &&
        (!raster_thread_merger_ || raster_thread_merger_->IsMerged())) {
      external_view_embedder_->SubmitFrame(surface_->GetContext(),
                                           std::move(frame));
    } else {
      frame->Submit();
    }

    return raster_status;
  }

  return RasterStatus::kFailed;
}

其中核心分为两步:

  1. external_view_embedder_->BeginFrame:Hybrid Composition 下,PlatformView 的帧绘制是从这里开始的
  2. external_view_embedder_->SubmitFrame:如果是 Hybrid Composition PlatformView 模式下,走 external_view_embedder_ 的上屏逻辑

Step3:AndroidExternalViewEmbedder BeginFrame

// AndroidExternalViewEmbedder
void AndroidExternalViewEmbedder::BeginFrame(
    SkISize frame_size,
    GrDirectContext* context,
    double device_pixel_ratio,
    fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {
  Reset();

  // ...
  surface_pool_->SetFrameSize(frame_size);
  // JNI method must be called on the platform thread.
  if (raster_thread_merger->IsOnPlatformThread()) {
    // 通过 JNI 调到 Java 侧
    jni_facade_->FlutterViewBeginFrame();
  }

  frame_size_ = frame_size;
  device_pixel_ratio_ = device_pixel_ratio;
}

// platform_view_android_jni_impl.cc
void PlatformViewAndroidJNIImpl::FlutterViewBeginFrame() {
  JNIEnv* env = fml::jni::AttachCurrentThread();
  auto java_object = java_object_.get(env);
  env->CallVoidMethod(java_object.obj(), g_on_begin_frame_method);
}

// FlutterJNI.java
@SuppressWarnings("unused")
@UiThread
public void onBeginFrame() {
  // ...
  platformViewsController.onBeginFrame();
}

// PlatformViewsController
public void onBeginFrame() {
  // 储存覆盖在 Platform View 之上的 FlutterUI
  currentFrameUsedOverlayLayerIds.clear();
  // 储存当前帧的 Platform View
  currentFrameUsedPlatformViewIds.clear();
}

其中,主要看到是做了一些清理。


Step4-1 光栅化准备 Preroll

Flutter 渲染流水线的《Step7-2 光栅化》步光栅化阶段,还有两步:

  1. layer_tree.Preroll
  2. view_embedder_->PostPrerollAction

先看 Preroll。如果在 LayerTree 中出现 Hybrid Composition 的 Layer,对应的是 PlatformViewLayer:

void PlatformViewLayer::Preroll(PrerollContext* context,
                                const SkMatrix& matrix) {
  set_paint_boundsMakeXYWH(offset_.x(), offset_.y(), size_.width(,
                                    size_.height()));

  context->has_platform_view = true;
  set_subtree_has_platform_view(true);
  std::unique_ptr<EmbeddedViewParams> params =
      std::make_unique<EmbeddedViewParams>(matrix, size_,
                                           context->mutators_stack);
  context->view_embedder->PrerollCompositeEmbeddedView(view_id_,
                                                       std::move(params));
}

void AndroidExternalViewEmbedder::PrerollCompositeEmbeddedView(
    int view_id,
    std::unique_ptr<EmbeddedViewParams> params) {
  //...

  // 用于创建 RTree 的工厂
  auto rtree_factory = RTreeFactory();
  // view_rtrees_ 存放 ViewId 对应的 RTree
  view_rtrees_.insert_or_assign(view_id, rtree_factory.getInstance());

  // `SkPictureRecorder`是Skia图形库中的一个类,它用于记录一系列的绘图命令,
  // 然后可以将这些命令保存到一个`SkPicture`对象中,以便在以后的时间点重播(replay)这些命令。
  auto picture_recorder = std::make_unique<SkPictureRecorder>();
  // 开始记录绘图操作
  // frame_size_:绘图区域
  // rtree_factory:RTree
  picture_recorder->beginRecordingMake(frame_size_), &rtree_factory;

  // picture_recorders_ 存放 ViewId 对应的 SkPictureRecorder
  picture_recorders_.insert_or_assign(view_id, std::move(picture_recorder));
  // 将视图ID添加到`composition_order_`列表的末尾。
  composition_order_.push_back(view_id);
  // 检查`view_params_`映射中是否已经存在相同的视图参数,如果存在并且参数没有改变,那么就直接返回。
  // Update params only if they changed.
  if (view_params_.count(view_id) == 1 &&
      view_params_.at(view_id) == *params.get()) {
    return;
  }
  // 将视图参数插入到`view_params_`映射中
  view_params_.insert_or_assign(view_id, EmbeddedViewParams(*params.get()));
}

在上面代码中,会将当前平台视图加入到 RTree 中,并且创建 SkPictureRecorder。


Step4-2 光栅化准备 PostPrerollAction

主要是与线程合并相关。

PostPrerollResult AndroidExternalViewEmbedder::PostPrerollAction(
    fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {
  if (!raster_thread_merger->IsMerged()) {
    // The raster thread merger may be disabled if the rasterizer is being
    // created or teared down.
    //
    // In such cases, the current frame is dropped, and a new frame is attempted
    // with the same layer tree.
    //
    // Eventually, the frame is submitted once this method returns `kSuccess`.
    // At that point, the raster tasks are handled on the platform thread.
    CancelFrame();
    raster_thread_merger->MergeWithLease(kDefaultMergedLeaseDuration);
    return PostPrerollResult::kSkipAndRetryFrame;
  }
  raster_thread_merger->ExtendLeaseTo(kDefaultMergedLeaseDuration);
  // Surface switch requires to resubmit the frame.
  // TODO(egarciad): https://github.com/flutter/flutter/issues/65652
  if (previous_frame_view_count_ == 0) {
    return PostPrerollResult::kResubmitFrame;
  }
  return PostPrerollResult::kSuccess;
}

Step5:上屏

这个方法的主要作用是提交一个帧,帧中可能包含多个平台视图(例如Android的View或Widget)和Flutter的UI。

这个方法的核心原理是使用Skia的绘图命令和RTree数据结构来处理平台视图和Flutter UI的交互。当Flutter UI与平台视图相交时,会创建一个覆盖层,这个覆盖层会覆盖在平台视图上,从而实现Flutter UI和平台视图的混合渲染。

如何理解 SubmitFrame?

在Flutter引擎中,"SubmitFrame"是一个重要的概念,它是渲染流程的一部分。当Flutter引擎完成一帧的绘制后,它会调用"SubmitFrame"方法将这一帧提交给底层的图形API(例如OpenGL或Vulkan),然后这一帧就会被显示在屏幕上

整体流程:

  1. 检查帧是否包含平台层:如果帧不包含任何平台层,那么直接提交帧并返回。

  2. 初始化变量:创建了几个用于存储视图信息的映射和列表,获取了帧的背景画布,保存了当前帧的视图数量。

  3. 保存和恢复画布状态:使用SkAutoCanvasRestore对象保存画布的状态,当对象销毁时(即方法返回时),画布的状态会自动恢复。

  4. 遍历所有视图:对于每一个视图,首先结束其绘图记录,获取其绘图命令和RTree(一个用于快速查询矩形交集的数据结构)。然后,检查这个视图的Flutter UI是否与之前的任何平台视图相交,如果相交,那么就将相交的区域添加到overlay_layers映射中,并从背景画布中剪切掉这个区域。最后,将视图的绘图命令绘制到背景画布上。

  5. 提交背景画布:如果在上一帧中有视图,那么就提交背景画布。

  6. 显示平台视图:对于每一个视图,首先获取其位置和大小,然后调用FlutterViewOnDisplayPlatformView方法显示或更新视图。如果视图有一个覆盖层,那么就创建一个新的表面(如果需要的话),并提交这个表面。

具体代码实现:

这里面的 View 和 ViewId 不只是平台视图的,也包括 Flutter UI 的

void AndroidExternalViewEmbedder::SubmitFrame(
    GrDirectContext* context,
    std::unique_ptr<SurfaceFrame> frame) {
  //...

  // 如果帧不包含任何平台层,那么直接提交帧并返回。
  if (!FrameHasPlatformLayers()) {
    frame->Submit();
    return;
  }

  std::unordered_map<int64_t, SkRect> overlay_layers;
  std::unordered_map<int64_t, sk_sp<SkPicture>> pictures;
  SkCanvas* background_canvas = frame->SkiaCanvas();
  auto current_frame_view_count = composition_order_.size();

  // 使用`SkAutoCanvasRestore`对象保存画布的状态,当对象销毁时(即方法返回时),画布的状态会自动恢复。
  SkAutoCanvasRestore save(background_canvas, /*doSave=*/true);

  // 遍历所有视图,具体指所有使用 HybridComposition 模式的平台视图
  for (size_t i = 0; i < current_frame_view_count; i++) {
    int64_t view_id = composition_order_[i];

    // 首先结束其绘图记录,获取其绘图命令和RTree
    sk_sp<SkPicture> picture =
        picture_recorders_.at(view_id)->finishRecordingAsPicture();
    // ...
    pictures.insert({view_id, picture});

    sk_sp<RTree> rtree = view_rtrees_.at(view_id);
    SkRect joined_rect = SkRect::MakeEmpty();

    // 然后,检查这个视图的Flutter UI是否与之前的任何平台视图相交,如果相交,
    // 那么就将相交的区域添加到`overlay_layers`映射中,并从背景画布中剪切掉这个区域。
    // 最后,将视图的绘图命令绘制到背景画布上。
    for (ssize_t j = i; j >= 0; j--) {
      int64_t current_view_id = composition_order_[j];
      SkRect current_view_rect = GetViewRect(current_view_id);
      // Each rect corresponds to a native view that renders Flutter UI.
      // 计算是否有相交
      std::list<SkRect> intersection_rects =
          rtree->searchNonOverlappingDrawnRects(current_view_rect);

      // Limit the number of native views, so it doesn't grow forever.
      // 优化手段
      // 这段代码的作用是将多个矩形区域合并为一个大的矩形区域,这个大的矩形区域是所有小矩形区域的并集。
      // 如果多个平台视图占用的屏幕位置是重合的话,会在原生侧复用一个 Overlay
      for (const SkRect& rect : intersection_rects) {
        joined_rect.join(rect);
      }
    }
    // 如果存在重合区域
    if (!joined_rect.isEmpty()) {
      // Subpixels in the platform may not align with the canvas subpixels.
      //
      // To workaround it, round the floating point bounds and make the rect
      // slightly larger.
      //
      // For example, {0.3, 0.5, 3.1, 4.7} becomes {0, 0, 4, 5}.
      // 这行代码将`joined_rect`的边界四舍五入到最接近的整数,并稍微扩大这个矩形区域,以确保它包含原始的浮点数边界。
      // `roundOut`方法返回一个新的矩形,这个矩形的边界是四舍五入并扩大后的结果。
      joined_rect.set(joined_rect.roundOut());
      // 注册到 overlay_layers
      overlay_layers.insert({view_id, joined_rect});
      // 码从背景画布中剪切掉`joined_rect`区域。
      background_canvas->clipRect(joined_rect, SkClipOp::kDifference);
    }
    // `drawPicture`是Skia图形库中`SkCanvas`类的一个方法,它接受一个`SkPicture`对象作为参数,
    // 然后在画布上按照`SkPicture`对象记录的绘图命令进行绘制。
    // 将图像绘制到 background_canvas
    // 将 UI 绘制到背景层上
    background_canvas->drawPicture(pictures.at(view_id));
  }
  // 在切换到覆盖层表面的OpenGL上下文之前,提交背景画布帧。
  // 说明了如果嵌入正在切换表面,那么应该跳过一帧,并在`PostPrerollAction`中指示这一帧必须被重新提交。
  auto should_submit_current_frame = previous_frame_view_count_ > 0;
  if (should_submit_current_frame) {
    frame->Submit();
  }

  for (int64_t view_id : composition_order_) {
    SkRect view_rect = GetViewRect(view_id);
    const EmbeddedViewParams& params = view_params_.at(view_id);
    // Display the platform view. If it's already displayed, then it's
    // just positioned and sized.
    // 通知原生侧展示视图
    jni_facade_->FlutterViewOnDisplayPlatformView(
        view_id,             //
        view_rect.x(),       //
        view_rect.y(),       //
        view_rect.width(),   //
        view_rect.height(),  //
        params.sizePoints().width() * device_pixel_ratio_,
        params.sizePoints().height() * device_pixel_ratio_,
        params.mutatorsStack()  //
    );
    // 为每个视图创建一个表面(如果需要的话),并在需要提交当前帧时提交这个表面。
    std::unordered_map<int64_t, SkRect>::const_iterator overlay =
        overlay_layers.find(view_id);
    if (overlay == overlay_layers.end()) {
      continue;
    }
    // 为视图创建一个表面
    // 这里的视图,指的是需要绘制在原生视图上面的 FLutter UI
    std::unique_ptr<SurfaceFrame> frame =
        CreateSurfaceIfNeeded(context,               //
                              view_id,               //
                              pictures.at(view_id),  //
                              overlay->second        //
        );
    // 如果需要提交当前帧,那么就调用`frame`对象的`Submit`方法,提交这个表面。
    if (should_submit_current_frame) {
      frame->Submit();
    }
  }
}

对于 background_canvas->drawPicture(pictures.at(view_id)); 理解:

对于 composition_order_ 的理解:


Step6:通知原生展示平台视图

jni_facade_->FlutterViewOnDisplayPlatformView 中,通知原生侧展示原生视图。

void PlatformViewAndroidJNIImpl::FlutterViewOnDisplayPlatformView(
    int view_id,
    int x,
    int y,
    int width,
    int height,
    int viewWidth,
    int viewHeight,
    MutatorsStack mutators_stack) {
  JNIEnv* env = fml::jni::AttachCurrentThread();
  auto java_object = java_object_.get(env);

  jobject mutatorsStack = env->NewObject(g_mutators_stack_class->obj(),
                                         g_mutators_stack_init_method);

  std::vector<std::shared_ptr<Mutator>>::const_iterator iter =
      mutators_stack.Begin();
  while (iter != mutators_stack.End()) {
    switch ((*iter)->GetType()) {
      case transform: {
        // ...
        break;
      }
      case clip_rect: {
        // ...
        break;
      }
      case clip_rrect: {
        // ...
        break;
      }
      // TODO(cyanglaz): Implement other mutators.
      // https://github.com/flutter/flutter/issues/58426
      case clip_path:
      case opacity:
        break;
    }
    ++iter;
  }

  env->CallVoidMethod(java_object.obj(), g_on_display_platform_view_method,
                      view_id, x, y, width, height, viewWidth, viewHeight,
                      mutatorsStack);
}

// FlutterJNI
public void onDisplayPlatformView(
    int viewId,
    int x,
    int y,
    int width,
    int height,
    int viewWidth,
    int viewHeight,
    FlutterMutatorsStack mutatorsStack) {
  ensureRunningOnMainThread();
  if (platformViewsController == null) {
    throw new RuntimeException(
        "platformViewsController must be set before attempting to position a platform view");
  }
  platformViewsController.onDisplayPlatformView(
      viewId, x, y, width, height, viewWidth, viewHeight, mutatorsStack);
}

// PlatformViewsController
public void onDisplayPlatformView(
    int viewId,
    int x,
    int y,
    int width,
    int height,
    int viewWidth,
    int viewHeight,
    FlutterMutatorsStack mutatorsStack) {
  initializeRootImageViewIfNeeded();
  initializePlatformViewIfNeeded(viewId);

  final FlutterMutatorView parentView = platformViewParent.get(viewId);
  parentView.readyToDisplay(mutatorsStack, x, y, width, height);
  parentView.setVisibility(View.VISIBLE);
  // `bringToFront`方法在Android的View类中用于将一个视图移到其父视图的前面。
  // 这意味着如果在屏幕上有多个视图重叠,调用此方法的视图会显示在最上层,因此会遮挡其他所有重叠的视图。
  parentView.bringToFront();

  final FrameLayout.LayoutParams layoutParams =
      new FrameLayout.LayoutParams(viewWidth, viewHeight);
  final View view = platformViews.get(viewId).getView();
  // 再把原生视图放在最上面
  if (view != null) {
    view.setLayoutParams(layoutParams);
    view.bringToFront();
  }
  currentFrameUsedPlatformViewIds.add(viewId);
}

其中:FlutterMutatorsStack 是需要合成的视图栈,包括 FlutterUI 底图,PlatformViews,FlutterUI 顶图。

所谓 FlutterUI 顶图,是有些 Flutter UI 需要展示在 PlatformViews 之上。


Step7-1:FlutterView 准备1:切换至 ImageView 模式

FlutterView 支持三种显示模式(SurfaceView、TextureView、ImageView)。ImageView 模式用于展示平台视图,这里切换至 ImageView 模式。

// PlatformViewsController
private void initializeRootImageViewIfNeeded() {
  if (synchronizeToNativeViewHierarchy && !flutterViewConvertedToImageView) {
    flutterView.convertToImageView();
    flutterViewConvertedToImageView = true;
  }
}

// FlutterView
public void convertToImageView() {
  renderSurface.pause();

  // 这里的 ImageView 是用于展示底图 UI 部分
  if (flutterImageView == null) {
    flutterImageView = createImageView();
    addView(flutterImageView);
  } else {
    flutterImageView.resizeIfNeeded(getWidth(), getHeight());
  }

  // 保存之前的 Surface,用于恢复
  previousRenderSurface = renderSurface;
  renderSurface = flutterImageView;
  if (flutterEngine != null) {
    renderSurface.attachToRenderer(flutterEngine.getRenderer());
  }
}

public FlutterImageView createImageView() {
  return new FlutterImageView(
      getContext(), getWidth(), getHeight(), FlutterImageView.SurfaceKind.background);
}

Step7-2:FlutterView 准备2:PlatformView 准备

PlatformView 是早已经创建好的,还有另一路 Channel 路径。我们前面分析的是渲染流水线的路径,对 Channel 路径在后面分析,搞懂流水线,对另一条路径就是小 Case 了。

// PlatformViewsController
void initializePlatformViewIfNeeded(int viewId) {
  // PlatformView 是早已经创建好的
  final PlatformView platformView = platformViews.get(viewId);
  if (platformView == null) {
    throw new IllegalStateException(
        "Platform view hasn't been initialized from the platform view channel.");
  }
  // ...
  // 创建一个 FlutterMutatorView 用于展示图层合成堆栈
  // 如果会有上层遮盖层,会再往这里面添加 ImageView
  final FlutterMutatorView parentView =
      new FlutterMutatorView(
          context, context.getResources().getDisplayMetrics().density, androidTouchProcessor);

  // 焦点设置
  parentView.setOnDescendantFocusChangeListener(
      (view, hasFocus) -> {
        if (hasFocus) {
          platformViewsChannel.invokeViewFocused(viewId);
        } else if (textInputPlugin != null) {
          textInputPlugin.clearPlatformViewClient(viewId);
        }
      });

  // 将 PlatformView 添加到 FlutterMutatorView
  // 将 FlutterMutatorView 添加到 FlutterView
  platformViewParent.put(viewId, parentView);
  parentView.addView(platformView.getView());
  flutterView.addView(parentView);
}

Step7-3:FlutterView 准备3:FlutterMutatorView 准备

当需要展示平台视图时,在前面的步骤会将渲染分为三个部分:位于原生视图之下的 Flutter 背景 UI、位于中间层的原生视图,以及盖在原生视图上的 FlutterUI。FlutterMutatorView 是一个布局容器,负责容纳后面两者。

// 配置偏移量
public void readyToDisplay(
    @NonNull FlutterMutatorsStack mutatorsStack, int left, int top, int width, int height) {
  this.mutatorsStack = mutatorsStack;
  this.left = left;
  this.top = top;
  FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(width, height);
  layoutParams.leftMargin = left;
  layoutParams.topMargin = top;
  setLayoutParams(layoutParams);
  setWillNotDraw(false);
}

接下来让 FlutterMutatorView 可见并位于最上层。


Step8:前景上屏

上面步骤完成了 Flutter UI 底图和 PlatformView 的上屏,但是还会有覆盖在 PlatformView 上面的 Flutter UI。它们也需要创建对应的 FlutterImageView 进行展示。这一步,由《Step7 rasterizer 绘制》draw 方法最后的这一部分来驱动:

// EndFrame should perform cleanups for the external_view_embedder.
if (surface_ && external_view_embedder_) {
  external_view_embedder_->EndFrame(should_resubmit_frame,
                                    raster_thread_merger_);
}
// AndroidExternalViewEmbedder
void AndroidExternalViewEmbedder::EndFrame(
    bool should_resubmit_frame,
    fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {
  surface_pool_->RecycleLayers();
  // JNI method must be called on the platform thread.
  if (raster_thread_merger->IsOnPlatformThread()) {
    jni_facade_->FlutterViewEndFrame();
  }
}

// platform_view_android_jni_impl.cc
void PlatformViewAndroidJNIImpl::FlutterViewEndFrame() {
  JNIEnv* env = fml::jni::AttachCurrentThread();

  auto java_object = java_object_.get(env);
  if (java_object.is_null()) {
    return;
  }

  env->CallVoidMethod(java_object.obj(), g_on_end_frame_method);
}

// FlutterJNI
@SuppressWarnings("unused")
@UiThread
public void onEndFrame() {
  ensureRunningOnMainThread();
  if (platformViewsController == null) {
    throw new RuntimeException(
        "platformViewsController must be set before attempting to end the frame");
  }
  platformViewsController.onEndFrame();
}

// PlatformViewsController
public void onEndFrame() {
  // If there are no platform views in the current frame,
  // then revert the image view surface and use the previous surface.
  //
  // Otherwise, acquire the latest image.
  if (flutterViewConvertedToImageView && currentFrameUsedPlatformViewIds.isEmpty()) {
    flutterViewConvertedToImageView = false;
    // 状态切换,从 ImageView 切回去
    flutterView.revertImageView(
        () -> {
          // Destroy overlay surfaces once the surface reversion is completed.
          finishFrame(false);
        });
    return;
  }
  // Whether the current frame was rendered using ImageReaders.
  //
  // Since the image readers may not have images available at this point,
  // this becomes true if all the required surfaces have images available.
  //
  // This is used to decide if the platform views can be rendered in the current frame.
  // If one of the surfaces doesn't have an image, the frame may be incomplete and must be
  // dropped.
  // For example, a toolbar widget painted by Flutter may not be rendered.
  final boolean isFrameRenderedUsingImageReaders =
      flutterViewConvertedToImageView && flutterView.acquireLatestImageViewFrame();
  finishFrame(isFrameRenderedUsingImageReaders);
}

// PlatformViewsController
private void finishFrame(boolean isFrameRenderedUsingImageReaders) {
  // 遍历覆盖在上面的 Flutter UI
  for (int i = 0; i < overlayLayerViews.size(); i++) {
    final int overlayId = overlayLayerViews.keyAt(i);
    // 取出对应的覆盖 FlutterImageView
    final FlutterImageView overlayView = overlayLayerViews.valueAt(i);

    // 遍历的是所有的,看当前帧里是否包含,如果包含的上屏展示,不包含的,隐藏
    if (currentFrameUsedOverlayLayerIds.contains(overlayId)) {
      flutterView.attachOverlaySurfaceToRender(overlayView);
      final boolean didAcquireOverlaySurfaceImage = overlayView.acquireLatestImage();
      isFrameRenderedUsingImageReaders &= didAcquireOverlaySurfaceImage;
    } else {
      // If the background surface isn't rendered by the image view, then the
      // overlay surfaces can be detached from the rendered.
      // This releases resources used by the ImageReader.
      if (!flutterViewConvertedToImageView) {
        overlayView.detachFromRenderer();
      }
      // Hide overlay surfaces that aren't rendered in the current frame.
      overlayView.setVisibility(View.GONE);
    }
  }

  // 再遍历一下 MutatorView,把这些 ImageView 展示出来
  for (int i = 0; i < platformViewParent.size(); i++) {
    final int viewId = platformViewParent.keyAt(i);
    final View parentView = platformViewParent.get(viewId);

    // This should only show platform views that are rendered in this frame and either:
    //  1. Surface has images available in this frame or,
    //  2. Surface does not have images available in this frame because the render surface should
    // not be an ImageView.
    //
    // The platform view is appended to a mutator view.
    //
    // Otherwise, hide the platform view, but don't remove it from the view hierarchy yet as
    // they are removed when the framework diposes the platform view widget.
    if (currentFrameUsedPlatformViewIds.contains(viewId)
        && (isFrameRenderedUsingImageReaders || !synchronizeToNativeViewHierarchy)) {
      parentView.setVisibility(View.VISIBLE);
    } else {
      parentView.setVisibility(View.GONE);
    }
  }
}

其中:flutterView.attachOverlaySurfaceToRender(overlayView); 将会触发 FlutterView 中的:

public void attachOverlaySurfaceToRender(FlutterImageView view) {
  if (flutterEngine != null) {
    view.attachToRenderer(flutterEngine.getRenderer());
  }
}

这样,覆盖在 PlatformView 上面的 Flutter UI 也展示出来了。


第二部分:Native 视图的创建

上面的分析过程,相当于 Hybrid composition 的渲染主线,但前面说到,具体的原生视图,在流水线渲染时已经提前创建好了。从本节开始,我们来分析另一条分支,原生视图的创建。让我们回到最开始的创建:

class FooPlatformView extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return PlatformViewLink(
       viewType: 'webview',
       onCreatePlatformView: createFooWebView,
       surfaceFactory: (BuildContext context, PlatformViewController controller) {
        return PlatformViewSurface(
            gestureRecognizers: gestureRecognizers,
            controller: controller,
            hitTestBehavior: PlatformViewHitTestBehavior.opaque,
        );
       },
       onCreatePlatformView: (PlatformViewCreationParams params) {
	      return PlatformViewsService.initSurfaceAndroidView(
	        id: params.id,
	        viewType: 'webview',
	        layoutDirection: TextDirection.ltr,
	        creationParams: params,
	        creationParamsCodec: StandardMessageCodec()
	      )..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
	       // 关键方法
	       ..create();
       }
    );
   }
}

其中的 create 方法比较关键。


initSurfaceAndroidView

我们先看 PlatformViewsService.initSurfaceAndroidView 方法的实现,创建了一个 SurfaceAndroidViewController 实例:

static SurfaceAndroidViewController initSurfaceAndroidView({
  required int id,
  required String viewType,
  required TextDirection layoutDirection,
  dynamic creationParams,
  MessageCodec<dynamic>? creationParamsCodec,
  VoidCallback? onFocus,
}) {
  final SurfaceAndroidViewController controller = SurfaceAndroidViewController._(
    viewId: id,
    viewType: viewType,
    layoutDirection: layoutDirection,
    creationParams: creationParams,
    creationParamsCodec: creationParamsCodec,
  );

  _instance._focusCallbacks[id] = onFocus ?? () {};
  return controller;
}

接下来回调上一节说到的 create。


SurfaceAndroidViewController 的 create

SurfaceAndroidViewController 继承自 AndroidViewController,SurfaceAndroidViewController 本身没有覆写 create,因此首先调用 AndroidViewController 的 create 方法:

// AndroidViewController
Future<void> create() async {
  assert(_state != _AndroidViewState.disposed, 'trying to create a disposed Android view');

  await _sendCreateMessage();

  _state = _AndroidViewState.created;
  for (final PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) {
    callback(viewId);
  }
}

其中,_sendCreateMessage 由 SurfaceAndroidViewController 实现,从这一步开始,开始通知 Native 创建原生视图:

@override
Future<void> _sendCreateMessage() {
  final Map<String, dynamic> args = <String, dynamic>{
    'id': viewId,
    'viewType': _viewType,
    'direction': AndroidViewController._getAndroidDirection(_layoutDirection),
    'hybrid': true,
  };
  if (_creationParams != null) {
    final ByteData paramsByteData =
        _creationParamsCodec!.encodeMessage(_creationParams)!;
    args['params'] = Uint8List.view(
      paramsByteData.buffer,
      0,
      paramsByteData.lengthInBytes,
    );
  }
  return SystemChannels.platform_views.invokeMethod<void>('create', args);
}

会调用 Channel,调用原生侧。


PlatformViewsChannel.create

这一步与《Flutter Android Virtual Display 实现原理》的《Flutter PlatformViewsChannel》一节是一致的。

在 Android 部分,MethodCallHandler.create 负责处理 Channel 请求,具体可参见《MethodCallHandler.create》方法。

区别在于,此处将走 usesHybridComposition 的分支,即调用 createAndroidViewForPlatformView


createAndroidViewForPlatformView

该方法的实现位于 PlatformViewsController 中的 channelHandler,这是一个匿名内部类实例,其中包括对 createAndroidViewForPlatformView 方法的实现。

// PlatformViewsController
public void createAndroidViewForPlatformView(
    @NonNull PlatformViewsChannel.PlatformViewCreationRequest request) {
  // API level 19 is required for `android.graphics.ImageReader`.
  ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT);

  //...

  final PlatformViewFactory factory = registry.getFactory(request.viewType);
  //...

  Object createParams = null;
  if (request.params != null) {
    createParams = factory.getCreateArgsCodec().decodeMessage(request.params);
  }

  final PlatformView platformView = factory.create(context, request.viewId, createParams);
  platformView.getView().setLayoutDirection(request.direction);
  platformViews.put(request.viewId, platformView);
}

可以看到,在这一步直接创建原生视图,并且将该视图放入 platformViews 中备用,在前文的渲染流水线主分支中,将从中取出创建好的视图,直接进行混合展示。


附录

PlatformViewLink 本身是一个 StatefulWidget,在 State 中有一个初始化方法:

// _PlatformViewLinkState
class _PlatformViewLinkState extends State<PlatformViewLink> {
  @override
  void initState() {
    _focusNode = FocusNode(debugLabel: 'PlatformView(id: $_id)');
    _initialize();
    super.initState();
  }

  void _initialize() {
    // 从 Flutter 侧创建一个唯一标识
    _id = platformViewsRegistry.getNextPlatformViewId();
	// 调用外界传入的 _onCreatePlatformView
    _controller = widget._onCreatePlatformView(
      PlatformViewCreationParams._(
        id: _id!,
        viewType: widget.viewType,
        onPlatformViewCreated: _onPlatformViewCreated,
        onFocusChanged: _handlePlatformFocusChanged,
      ),
    );
  }
// ...
}

结合上一节来看,_onCreatePlatformView 是在 PlatformViewLink 创建时传入的:

onCreatePlatformView: (PlatformViewCreationParams params) {
  return PlatformViewsService.initSurfaceAndroidView(
	id: params.id,
	viewType: 'webview',
	layoutDirection: TextDirection.ltr,
	creationParams: params,
	creationParamsCodec: StandardMessageCodec()
  )..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
   ..create();
}

PlatformViewsService.initSurfaceAndroidView 创建出一个 PlatformViewController 实例,存在 _PlatformViewLinkState 进行使用。


SurfaceAndroidViewController 的使用

接下来我们回到 _PlatformViewLinkState 中,分析 SurfaceAndroidViewController 是如何被使用的。

_PlatformViewLinkState 的 build 方法中,回调了 surfaceFactory,并向其中传入了 _controller

@override
Widget build(BuildContext context) {
  if (!_platformViewCreated) {
    return const SizedBox.expand();
  }
  _surface ??= widget._surfaceFactory(context, _controller!);
  return Focus(
    focusNode: _focusNode,
    onFocusChange: _handleFrameworkFocusChanged,
    child: _surface!,
  );
}

对应于上面使用代码的:

surfaceFactory: (BuildContext context, PlatformViewController controller) {
	return PlatformViewSurface(
		gestureRecognizers: gestureRecognizers,
		controller: controller,
		hitTestBehavior: PlatformViewHitTestBehavior.opaque,
);

PlatformViewSurface 中,又进一步将 controller 传入 PlatformViewRenderBox:

// PlatformViewSurface
@override
RenderObject createRenderObject(BuildContext context) {
  return PlatformViewRenderBox(controller: controller, gestureRecognizers: gestureRecognizers, hitTestBehavior: hitTestBehavior);
}

继续进入 PlatformViewRenderBox,这是一个 RenderObject,在 paint 方法中,会添加一个 PlatformViewLayer。


Flutter 侧代码实现

我们按照自底向上的方式,从最底层实现开始,一层层向上,最终到达开发者熟悉的 PlatformViewLink

Flutter PlatformViewLink

Flutter PlatformViewSurface

Flutter PlatformViewRenderBox

Flutter PlatformViewLayer

Flutter PlatformViewsService

Flutter SurfaceAndroidViewController


C++ 侧代码实现

Flutter AndroidExternalViewEmbedder》:基类《Flutter ExternalViewEmbedder


本文作者:Maeiee

本文链接:Flutter Android Hybrid composition 实现原理

版权声明:如无特别声明,本文即为原创文章,版权归 Maeiee 所有,未经允许不得转载!


喜欢我文章的朋友请随缘打赏,鼓励我创作更多更好的作品!